Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support separate node announcement addresses #484

Merged
merged 3 commits into from
Mar 28, 2025

Conversation

aylagreystone
Copy link
Contributor

Fixes #422

Users running ldk-node in a container and/or behind a NAT will often want to or be forced to bind to a different address than they announce to the network.

This PR introduces a new announcement_addresses configuration option that operates the same way listening_addresses does but is used for constructing the node announcement message. An empty announcement_addresses will operate exactly the same way as today by defaulting to listening_addresses.

Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into this!

Already looks pretty good, one comment though.

Copy link
Collaborator

@tnull tnull Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll also need to account for the new field in may_announce_channel (and corresponding tests) in this file. We use this method to check whether we allow to open or accept announced channels.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at this but I believe the may_announce_channel is still correct as-is. announcement_addresses doesn't change the behavior of when or if a node can announce channels. That is still purely determined by listening_addresses and alias. The announcement_addresses just allows an override for what addresses are actually announced.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you are right! In fact, could you extend the node_announce_channel test case to assert that may_announce_channel is still false if listening_addresses is None and announcement_addresses is Some?

May also be good to add a check for this and log+abort early in Builder::build_with_store_internal, so that the user can't ever startup with misconfigured Node where announcement_addresses is set but listening_addresses aren't.

Copy link
Contributor Author

@aylagreystone aylagreystone Mar 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of those suggestions sound good. For the misconfigured node do we want a new error variant or should I re-use the existing BuildError::InvalidAnnouncementAddresses? I think it's probably fine as long as there's a log_error with explanation alongside it.

Also, I think the check should not only be against unset listening_addresses but actually be if config.announcement_addrresses.is_some() && !may_announce_channel(&config).

Basically if you set announcement addresses but the node will not announce then it's misconfigured.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of those suggestions sound good. For the misconfigured node do we want a new error variant or should I re-use the existing BuildError::InvalidAnnouncementAddresses? I think it's probably fine as long as there's a log_error with explanation alongside it.

Yeah, though InvalidListeningAddresses might make more sense, no?

Basically if you set announcement addresses but the node will not announce then it's misconfigured.

Yeah, that makes sense. FWIW, could do the same for NodeAlias, as you'd also only expect it to be set if you want to announce your node.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well if the check is if config.announcement_addrresses.is_some() && !may_announce_channel(&config) then we don't know if it's missing listening_addresses or alias and so maybe it makes more sense for the error to be BuildError::InvalidAnnouncementAddresses` in the sense they are invalid because you didn't set the other values? Or we could add a new error specifically for some announcement field set (announcement_addresses or alias) when the node is not going to announce.

Will add the same check for NodeAlias.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, maybe we should switch may_announce_channel to return a Result<(), AnnounceError>? Then, we know at the callsites why it can't announce and can take action (mostly log the right thing before returning the corresponding BuildError) based on the error type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave this a shot in the latest commit. The error message handling feels a bit verbose now but I tried to dry it up best I could. Check it out and let me know what you think.

@@ -412,6 +417,18 @@ impl NodeBuilder {
Ok(self)
}

/// Sets the IP address and TCP port which [`Node`] will announce to the gossip network that it accepts connections on.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add the 'if unset' note here as well?

src/lib.rs Outdated
@@ -457,7 +457,9 @@ impl Node {
continue;
}

let addresses = if let Some(addresses) = bcast_config.listening_addresses.clone() {
let addresses = if let Some(addresses) = bcast_config.announcement_addresses.clone() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use var names such as listening_addresses and announcement_addresses for better readability.

src/lib.rs Outdated
@@ -457,7 +457,9 @@ impl Node {
continue;
}

let addresses = if let Some(addresses) = bcast_config.listening_addresses.clone() {
let addresses = if let Some(addresses) = bcast_config.announcement_addresses.clone() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need to update may_announce_channel and also the code-points where we check for availability of listening addresses for public-channel/announcements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we do. I mentioned this above:

I looked at this but I believe the may_announce_channel is still correct as-is. announcement_addresses doesn't change the behavior of when or if a node can announce channels. That is still purely determined by listening_addresses and alias. The announcement_addresses just allows an override for what addresses are actually announced.

let addresses = if let Some(addresses) = bcast_config.listening_addresses.clone() {
let addresses = if let Some(addresses) = bcast_config.announcement_addresses.clone() {
addresses
} else if let Some(addresses) = bcast_config.listening_addresses.clone() {
addresses
} else {
debug_assert!(false, "We checked whether the node may announce, so listening addresses should always be set");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this log will need to be updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it does because listening_addresses is the only thing that should always be set.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you are right! In fact, could you extend the node_announce_channel test case to assert that may_announce_channel is still false if listening_addresses is None and announcement_addresses is Some?

May also be good to add a check for this and log+abort early in Builder::build_with_store_internal, so that the user can't ever startup with misconfigured Node where announcement_addresses is set but listening_addresses aren't.

src/lib.rs Outdated
@@ -802,6 +804,11 @@ impl Node {
self.config.listening_addresses.clone()
}

/// Returns our own announcement addresses.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this always return the addresses we announce, i.e., fallback to listening_addresses if announcement_addresses are None?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I think that probably makes more sense? If they want to see what the actual announcement_addresses are they can check the node config.

@aylagreystone
Copy link
Contributor Author

Pushed new commits that address all review feedback. Only consideration left imo is whether or not to introduce new error variant to represent misconfigured announcement parameters.

Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excuse the delay here.

Only consideration left imo is whether or not to introduce new error variant to represent misconfigured announcement parameters.

Yeah, that could make sense to have a catchall BuildError that represents any of the failure cases.

The changes mostly look good to me, but we should try to make them a bit less verbose/less redundant. So please just implement Display for AnnounceError.

Also, please clean up your git history. Each feature commit should have a short one-line description followed by a body describing what the change does and why it was necessary (see https://cbea.ms/git-commit/ for a rough guideline). I think it would also make sense to squash some of these commits already.

src/builder.rs Outdated
use crate::{io, NodeMetrics};
use crate::{may_announce_channel, Node};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Please import may_announce_channel from config, not the crate root.

src/builder.rs Outdated
@@ -412,6 +417,20 @@ impl NodeBuilder {
Ok(self)
}

/// Sets the IP address and TCP port which [`Node`] will announce to the gossip network that it accepts connections on.
///
/// **Note**: If unset, the `listening_addresses` will be used as the list of addresses to announce.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we link to listening_addresses? This is not only convenient for users, but also asserts we'll get cargo doc` failures if the link would ever break going forward.

@@ -766,6 +785,13 @@ impl ArcedNodeBuilder {
self.inner.write().unwrap().set_listening_addresses(listening_addresses).map(|_| ())
}

/// Sets the IP address and TCP port which [`Node`] will announce to the gossip network that it accepts connections on.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please align the docs on the uniffi variant with the non-uniffi variant.

src/builder.rs Outdated
let error_msg_for = |prefix: &str| -> String {
match err {
AnnounceError::MissingNodeAlias =>
format!("{} set but the node will not announce itself as node alias is not configured. Please set a node alias.", prefix),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we always need to specify what is set, but only the failure condition. To this end, please just implement Display for AnnounceError rather than introducing this custom closure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm okay, might need help with the error message because to me the failure condition is that some but not all of the fields required to enable node announcement were set.

We don't really know if their intention was to have the node be announced or not and so just telling them to set node alias or that node alias wasn't configured makes it sound like it's required but it's really only required if they intend to have their node announced.

Not sure if I'm making sense but I will go ahead with implementing Display for AnnounceError and we can finalize the actual messages later.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm okay, might need help with the error message because to me the failure condition is that some but not all of the fields required to enable node announcement were set.

We don't really know if their intention was to have the node be announced or not and so just telling them to set node alias or that node alias wasn't configured makes it sound like it's required but it's really only required if they intend to have their node announced.

Ah, I see your point. Well we can still implement Display for AnnounceError, while adding (some, concise) context while logging.

Not sure if I'm making sense but I will go ahead with implementing Display for AnnounceError and we can finalize the actual messages later.

SGTM

@aylagreystone aylagreystone force-pushed the announced-addresses branch 2 times, most recently from 36b6ca4 to c98a403 Compare March 27, 2025 16:49
@aylagreystone
Copy link
Contributor Author

Ok I incorporated the feedback and added a Display implementation for AnnounceError which simplified some of the error logging.

I also went back and cleaned up my commit history.

Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks for addressing the feedback so far!

It would be great if we could add a new test case that simply sets up two nodes, opens an announced channel, and checks that the counterparty will indeed see the correct announcement_addresses and node_alias set in their network graph once it's updated. You should be able to basically copy the setup part from simple_bolt12_send_receive test, as there we already open an announced channel and wait until it propagates. Mind taking a stab at that in a separate/third commit?

src/config.rs Outdated
@@ -117,6 +117,10 @@ pub struct Config {
/// **Note**: We will only allow opening and accepting public channels if the `node_alias` and the
/// `listening_addresses` are set.
pub listening_addresses: Option<Vec<SocketAddress>>,
/// The addresses which the node will announce to the gossip network that it accepts connections on.
///
/// **Note**: If unset, the `listening_addresses` will be used as the list of addresses to announce.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Also link listening_addresses here.

src/builder.rs Outdated
if let Err(err) = may_announce_channel(&config) {
if config.announcement_addresses.is_some() {
log_error!(logger, "Announcement addresses were set but some required configuration options for node announcement are missing: {}", err);
return Err(match err {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Let's use a variable for the returned error and use the matches! macro.

src/config.rs Outdated
pub(crate) enum AnnounceError {
MissingNodeAlias,
MissingListeningAddresses,
MissingBoth,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename this MissingAliasAndAddresses.

@aylagreystone
Copy link
Contributor Author

Cool, thanks for addressing the feedback so far!

It would be great if we could add a new test case that simply sets up two nodes, opens an announced channel, and checks that the counterparty will indeed see the correct announcement_addresses and node_alias set in their network graph once it's updated. You should be able to basically copy the setup part from simple_bolt12_send_receive test, as there we already open an announced channel and wait until it propagates. Mind taking a stab at that in a separate/third commit?

Have fixed the nits you mentioned above and pushed a new commit that adds a new integration test that configures one node with both listening and announcement addresses and the other node with just listening addresses and asserts that they both receive the expected addresses.

src/builder.rs Outdated
///
/// **Note**: If unset, the [`listening_addresses`] will be used as the list of addresses to announce.
///
/// [`listening_addresses`]: NodeBuilder::set_listening_addresses
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use Self::set_listening_addresses here as NodeBuilder is private under the uniffi feature, hence this breaking doc builds.


// Assert that the aliases and addresses match the expected values
assert_eq!(
node_a_info.announcement_info.as_ref().unwrap().alias(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is failing CI as under the uniffi feature the API behaves slightly differntly:

error[E0599]: no method named `alias` found for reference `&ldk_node::graph::NodeAnnouncementInfo` in the current scope
   --> tests/integration_tests_rust.rs:950:51
    |
950 |         node_a_info.announcement_info.as_ref().unwrap().alias(),
    |                                                         ^^^^^-- help: remove the arguments
    |                                                         |
    |                                                         field, not a method

error[E0599]: no method named `addresses` found for reference `&ldk_node::graph::NodeAnnouncementInfo` in the current scope
   --> tests/integration_tests_rust.rs:954:51
    |
954 |         node_a_info.announcement_info.as_ref().unwrap().addresses(),
    |                                                         ^^^^^^^^^-- help: remove the arguments
    |                                                         |
    |                                                         field, not a method

error[E0599]: no method named `alias` found for reference `&ldk_node::graph::NodeAnnouncementInfo` in the current scope
   --> tests/integration_tests_rust.rs:959:51
    |
959 |         node_b_info.announcement_info.as_ref().unwrap().alias(),
    |                                                         ^^^^^-- help: remove the arguments
    |                                                         |
    |                                                         field, not a method

error[E0599]: no method named `addresses` found for reference `&ldk_node::graph::NodeAnnouncementInfo` in the current scope
   --> tests/integration_tests_rust.rs:963:51
    |
963 |         node_b_info.announcement_info.as_ref().unwrap().addresses(),
    |                                                         ^^^^^^^^^-- help: remove the arguments
    |                                                         |
    |                                                         field, not a method

We probably need a feature gate here that makes the checks use one or the other API depending on whether uniffi is set or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, just saw these uniffi issues. Will set up separate feature-gated asserts to handle it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, fixed the test and looks like CI is passing.

In certain node configurations a user might want or need to
announce different addresses to the network than the ones they
bind their tcp listeners to.
This error type can provide more context to callers about why
the node configuration does not support channel announcements.
A new integration test that configures nodes using the new
announcement addresses configuration.  Ensures that both nodes
receive the correct alias and address for nodes using and not
using this new feature.
Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much! LGTM!

@tnull tnull merged commit 7528e91 into lightningdevkit:main Mar 28, 2025
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add an announcement_address field to node config
3 participants